package com.hlg.xboot.web.controller;

import com.hlg.xboot.core.QuerySuggest;
import com.hlg.xboot.exceptions.ParameterException;
import com.hlg.xboot.exceptions.ServerBizException;
import com.hlg.xboot.model.AbstractModel;
import com.hlg.xboot.service.BaseService;
import com.hlg.xboot.utils.ExcelHelper;
import com.hlg.xboot.vo.RestBody;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Rest接口基类
 * Created by yangxin on 2017/5/3.
 */
public abstract class RestBaseController<E extends AbstractModel, S extends BaseService> extends BaseController {

    @Autowired
    protected S service;

    @RequestMapping(method = RequestMethod.GET)
    public RestBody list(E e, Pageable page, HttpServletRequest request, HttpServletResponse response) throws Exception {
        logger.debug(page.toString());
        return success(service.find(e, page));
    }

    @RequestMapping(value = "/all", method = RequestMethod.GET)
    public RestBody list(E e, Sort sort, HttpServletRequest request, HttpServletResponse response) throws Exception {
        return success(service.findAll(e, sort));
    }

    @RequestMapping(method = RequestMethod.POST)
    public RestBody add(@Valid E e, BindingResult result, HttpServletRequest request, HttpServletResponse response) throws Exception {
        return success(service.add(e));
    }

    @RequestMapping(value = "/resources", method = RequestMethod.POST)
    public RestBody add(@Valid E e, BindingResult result, @RequestParam("files") MultipartFile[] files, HttpServletRequest request, HttpServletResponse response) throws Exception {
        return success(service.add(e, files));
    }

    @RequestMapping(method = RequestMethod.PUT)
    public RestBody update(@Valid E e, BindingResult result, HttpServletRequest request, HttpServletResponse response) throws Exception {
        service.update(e);
        return success();
    }

    @RequestMapping(value = "/resources", method = RequestMethod.PUT)
    public RestBody update(@Valid E e, BindingResult result, @RequestParam("files") MultipartFile[] files, HttpServletRequest request, HttpServletResponse response) throws Exception {
        service.update(e, files);
        return success();
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public RestBody delete(@PathVariable Serializable id, HttpServletRequest request, HttpServletResponse response) throws Exception {
        service.deleteById(id);
        return success();
    }

    @RequestMapping(value = "/batch", method = RequestMethod.DELETE, params = {"ids"})
    public RestBody delete(@RequestParam String ids, HttpServletRequest request, HttpServletResponse response) throws Exception {
        service.deleteInBatch(ids);
        return success();
    }

    @PostMapping(value = "/batchImport")
    public RestBody batchImport(E e, BindingResult result, @RequestParam("file") MultipartFile file, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Integer count = service.batchImport(e, ExcelHelper.readExcel(file.getInputStream()), getLoginUser(request));
        Map<String, Integer> data = new HashMap();
        data.put("count", count);
        return success(data);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public RestBody get(@PathVariable Serializable id, HttpServletRequest request, HttpServletResponse response) throws Exception {
        return success(service.findById(id));
    }

    /**
     * 根据对象ID，从session中获取登录用户信息，新增或者删除记录，具体实现在之类中
     * @param id 对象ID
     * @param request
     * @return
     * @throws Exception
     */
    @PutMapping(value="/toggle/{id}")
    public RestBody addOrDelete(@PathVariable Serializable id, HttpServletRequest request) throws Exception {
        return success();
    }

    /**
     *  根据对象ID，从session中获取登录用户信息，判断是否信息已经存在了
     * @param id 对象ID
     * @param request
     * @return
     * @throws Exception
     */
    @PutMapping(value="/exist/{id}")
    public RestBody existRecord(@PathVariable Serializable id, HttpServletRequest request) throws Exception {
        return success();
    }

    /**
     * 搜索建议,模型需要实现QuerySuggest接口
     * @param e
     * @return 返回搜索建议
     * @throws Exception
     */
    @RequestMapping(value = "/suggest", method = RequestMethod.GET)
    public RestBody suggest(E e, Pageable page) throws Exception {
        if (!(e instanceof QuerySuggest)) {
            throw new ServerBizException("该接口不支持搜索建议");
        }
        List<HashMap<String, Object>> clubs = (List<HashMap<String, Object>>) service.find(e, page).getContent().stream().map((item) -> {
            HashMap<String, Object> map = new HashMap<String, Object>();
            QuerySuggest querySuggest = (QuerySuggest) item;
            map.put("id", querySuggest.getValue());
            map.put("name", querySuggest.getText());
            return map;
        }).collect(Collectors.toList());
        return success(clubs);
    }

    /**
     * 只修改某个属性
     * @param name  属性
     * @param value  值
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "/update/{id}/{name}/{value}", method = RequestMethod.PUT)
    public RestBody updateField(@PathVariable Serializable id, @PathVariable String name, @PathVariable String value, HttpServletRequest request) throws Exception {
        E model = (E) service.findById(id);
        setFieldValue(model, name, value);
        service.update(model);
        return success();
    }

    @RequestMapping(value = "/batch_update/{name}/{value}", method = RequestMethod.PUT, params = {"ids"})
    public RestBody batchUpdateField(String ids, @PathVariable String name, @PathVariable String value, HttpServletRequest request) throws Exception {
        List<Serializable> list = Arrays.asList(ids.split(","));
        for (Serializable id : list) {
            E model = (E) service.findById(id);
            setFieldValue(model, name, value);
            service.update(model);
        }
        return success();
    }

    /**
     * 修改模型指定字段的值
     * @param model 模型
     * @param name  字段名称
     * @param value 字段值
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    protected void setFieldValue(E model, String name, String value) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        String setMethodName = "set" + StringUtils.capitalize(name);
        try {
            Field field = getDeclaredField(model, name);
            String fieldType = field.getType().getName();
            if (fieldType.equals(Integer.class.getName())) {
                Method method = getDeclaredMethod(model, setMethodName,Integer.class);
                method.invoke(model,Integer.valueOf(value));
            } else if (fieldType.equals(Double.class.getName())) {
                Method method = getDeclaredMethod(model, setMethodName,Double.class);
                method.invoke(model,Double.valueOf(value));
            } else if (fieldType.equals(Float.class.getName())) {
                Method method = getDeclaredMethod(model, setMethodName,Float.class);
                method.invoke(model,Float.valueOf(value));
            } else if (fieldType.equals(Boolean.class.getName())) {
                Method method = getDeclaredMethod(model, setMethodName,Boolean.class);
                method.invoke(model,Boolean.valueOf(value));
            } else if (fieldType.equals(Long.class.getName())) {
                Method method = getDeclaredMethod(model, setMethodName,Long.class);
                method.invoke(model,Long.valueOf(value));
            } else if (fieldType.equals(Short.class.getName())) {
                Method method = getDeclaredMethod(model, setMethodName,Short.class);
                method.invoke(model,Long.valueOf(value));
            } else if (fieldType.equals(String.class.getName())) {
                Method method = getDeclaredMethod(model, setMethodName,String.class);
                method.invoke(model, value);
            }
        } catch (NoSuchFieldException e) {
            throw new ParameterException("无效的属性：" + name);
        } catch (IllegalArgumentException e) {
            throw new ParameterException("属性值类型不正确： " + value);
        } catch (NoSuchMethodException e) {
            throw new ParameterException(String.format("【%s】属性不允许修改,未找到%s方法", name, setMethodName));
        }
    }


    private Field getDeclaredField(E model, String fieldName) throws NoSuchFieldException {
        Field field = null;
        Class<?> clazz = model.getClass();
        for(; clazz != AbstractModel.class; clazz = clazz.getSuperclass()) {
            try {
                field = clazz.getDeclaredField(fieldName);
                return field;
            } catch (Exception e) {

            }
        }
        return null;
    }

    private Method getDeclaredMethod(E model, String methodName, Class<?> ... parameterTypes) throws NoSuchMethodException {
        Method method = null ;
        Class<?> clazz = model.getClass();
        for(; clazz != AbstractModel.class; clazz = clazz.getSuperclass()) {
            try {
                method = clazz.getDeclaredMethod(methodName, parameterTypes);
                method.setAccessible(true);
                return method;
            } catch (Exception e) {

            }
        }
        return null;
    }

}
